package com.qingzhuge.pay.service.impl;

import com.github.binarywang.wxpay.bean.coupon.*;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxScanPayNotifyResult;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.qingzhuge.common.utils.DateUtil;
import com.qingzhuge.common.DtoConverter;
import com.qingzhuge.exception.ConfigException;
import com.qingzhuge.pay.EnumPayType;
import com.qingzhuge.pay.dto.PayOrderDto;
import com.qingzhuge.pay.dto.PayRefundDto;
import com.qingzhuge.pay.entity.SysPayProperty;
import com.qingzhuge.pay.entity.support.*;
import com.qingzhuge.pay.service.IPayCallbackService;
import com.qingzhuge.pay.service.ISysPayPropertiesService;
import com.qingzhuge.pay.service.ISysPayWxService;
import com.qingzhuge.pay.utils.PayWxServiceFactory;
import com.qingzhuge.pay.vo.*;
import com.qingzhuge.service.cache.BaseServiceCache;
import jdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;

import static com.qingzhuge.common.Constants.DEF_PAY_ID;
import static com.qingzhuge.pay.service.impl.SysPayWxServiceImpl.CACHE_NAME;

/**
 * @author zero.xiao
 */
@Slf4j
@Service
@CacheConfig(cacheNames = CACHE_NAME)
public class SysPayWxServiceImpl extends BaseServiceCache<SysPayProperty> implements ISysPayWxService {

    static final String CACHE_NAME = "mallOrderService";
    @Resource
    private ISysPayPropertiesService payService;

    private final IPayCallbackService payCallbackService;

    public SysPayWxServiceImpl(
            @Autowired(required = false)
                    IPayCallbackService payCallbackService) {
        this.payCallbackService = payCallbackService;
    }

    private WxPayService getWxService(Long payId) {
        SysPayProperty payProperties = getPayProperties(payId);
        return PayWxServiceFactory.build(payProperties);
    }

    @Override
    public SysPayProperty getPayProperties(Long payId) {
        String cacheKey = getCacheKey(payId);
        SysPayProperty payProperties = getCache(cacheKey);
        if (null == payProperties) {
            synchronized (this) {
                log.debug("pay_id:{}", payId);
                if (null == payId || Objects.equals(DEF_PAY_ID, payId)) {
                    payProperties = payService.findWx();
                } else {
                    payProperties = payService.findByIdAndTypeAndEnableIsFalse(payId, EnumPayType.weixin);
                }
                if (null != payProperties) {
                    saveCache(cacheKey, payProperties);
                } else {
                    throw new ConfigException();
                }
            }
        }
        return payProperties;
    }

    private String getCacheKey(Long payId) {
        if (null != payId && !Objects.equals(DEF_PAY_ID, payId)) {
            return payId.toString();
        }
        return String.format("pay_wx_%s", DEF_PAY_ID);
    }


    /**
     * <pre>
     * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
     * 该接口提供所有微信支付订单的查询，商户可以通过查询订单接口主动查询订单状态，完成下一步的业务逻辑。
     * 需要调用查询接口的情况：
     * ◆ 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知；
     * ◆ 调用支付接口后，返回系统错误或未知交易状态情况；
     * ◆ 调用被扫支付API，返回USERPAYING的状态；
     * ◆ 调用关单或撤销接口API之前，需确认支付状态；
     * 接口地址：https://api.mch.weixin.qq.com/pay/orderquery
     * </pre>
     *
     * @param query 查询订单
     */
    @Override
    public WxPayOrderQueryResult queryOrder(PayOrderQuery query) throws WxPayException {
        WxPayOrderQueryRequest request = new WxPayOrderQueryRequest();
        request.setVersion(StringUtils.trimToNull(query.getVersion()));
        request.setOutTradeNo(StringUtils.trimToNull(query.getOutTradeNo()));
        request.setTransactionId(StringUtils.trimToNull(query.getTransactionId()));
        return this.getWxService(query.getPayId()).queryOrder(request);
    }

    /**
     * <pre>
     * 关闭订单
     * 应用场景
     * 以下情况需要调用关单接口：
     * 1. 商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
     * 2. 系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
     * 注意：订单生成后不能马上调用关单接口，最短调用时间间隔为5分钟。
     * 接口地址：https://api.mch.weixin.qq.com/pay/closeorder
     * 是否需要证书：   不需要。
     * </pre>
     */
    @Override
    public boolean closeOrder(Long pid, Long orderId) {
        if (null != payCallbackService) {
            if (null != orderId) {
                WxPayOrderCloseResult closeOrder = null;
                try {
                    closeOrder = this.getWxService(pid).closeOrder(String.valueOf(orderId));
                } catch (WxPayException e) {
                    e.printStackTrace();
                    log.error("close Order exception {}", e.getXmlString());
                }
                return payCallbackService.parseCloseOrderResult(closeOrder, orderId);
            }
        }
        return false;
    }


    /**
     * 调用统一下单接口，并组装生成支付所需参数对象.
     *
     * @param <T>   请使用{@link com.github.binarywang.wxpay.bean.order}包下的类
     * @param order 统一下单请求参数
     * @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象
     */
    @Override
    public <T, D extends WxResult> D createOrder(PayOrderDto order) {
        WxPayUnifiedOrderRequest request = getWxPayUnifiedOrderRequest(order);
        WxPayService wxService = this.getWxService(order.getPayId());
        try {
            T serviceOrder = wxService.createOrder(request);
            if (Objects.equals(WxPayConstants.TradeType.MWEB, request.getTradeType())) {
                log.debug("调用统一下单接口 MWEB : {}", serviceOrder);
                return toDto(serviceOrder, QingTingWxPayMwebOrderResult.class);
            } else if (Objects.equals(WxPayConstants.TradeType.JSAPI, request.getTradeType())) {
                log.debug("调用统一下单接口 JSAPI : {}", serviceOrder);
                return toDto(serviceOrder, QingTingWxPayMpOrderResult.class);
            } else if (Objects.equals(WxPayConstants.TradeType.NATIVE, request.getTradeType())) {
                log.debug("调用统一下单接口 NATIVE : {}", serviceOrder);
                return toDto(serviceOrder, QingTingWxPayNativeOrderResult.class);
            } else if (Objects.equals(WxPayConstants.TradeType.APP, request.getTradeType())) {
                log.debug("调用统一下单接口 APP : {}", serviceOrder);
                return toDto(serviceOrder, QingTingWxPayAppOrderResult.class);
            } else {
                log.debug("调用统一下单接口 : {}", serviceOrder);
            }
            return null;
        } catch (WxPayException e) {
            e.printStackTrace();
            return null;
        }
    }

    private <T, D extends WxResult> D toDto(T t, Class<? extends WxResult> bean) {
        D result = (D) new DtoConverter() {
        }.doBackward(t, bean);
        return result;
    }

    /**
     * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
     * 在发起微信支付前，需要调用统一下单接口，获取"预支付交易会话标识"
     * 接口地址：https://api.mch.weixin.qq.com/pay/unifiedorder
     *
     * @param order 请求对象，注意一些参数如appid、mchid等不用设置，方法内会自动从配置对象中获取到（前提是对应配置中已经设置）
     */
    @Override
    public WxPayUnifiedOrderResult unifiedOrder(PayOrderDto order) throws WxPayException {
        WxPayUnifiedOrderRequest request = getWxPayUnifiedOrderRequest(order);
        WxPayService wxService = this.getWxService(order.getPayId());
        return wxService.unifiedOrder(request);
    }

    private WxPayUnifiedOrderRequest getWxPayUnifiedOrderRequest(PayOrderDto order) {
        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        // 1、取得本地时间：
        Calendar nowTime = Calendar.getInstance();
        nowTime.setTime(order.getCreateTime());
        // 2、取得时间偏移量：
        int zoneOffset = nowTime.get(Calendar.ZONE_OFFSET);
        // 3、取得夏令时差：
        int dstOffset = nowTime.get(Calendar.DST_OFFSET);
        // 4、从本地时间里扣除这些差量，即可以取得UTC时间：
        nowTime.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));
        request.setTimeStart(DateUtil.getLocalTimeFromUTC(nowTime.getTime(), DateUtil.DATE_PATTERN.YYYYMMDDHHMMSS));
        nowTime.add(Calendar.MINUTE, 30);
        request.setTimeExpire(DateUtil.getLocalTimeFromUTC(nowTime.getTime(), DateUtil.DATE_PATTERN.YYYYMMDDHHMMSS));
        request.setVersion(StringUtils.trimToNull(order.getVersion()));
        request.setDeviceInfo(StringUtils.trimToNull(order.getDeviceInfo()));
        request.setBody(StringUtils.trimToNull(order.getBody()));
        request.setAttach(StringUtils.trimToNull(order.getAttach()));
        request.setOutTradeNo(StringUtils.trimToNull(order.getOutTradeNo()));
        request.setFeeType(StringUtils.trimToNull(order.getFeeType()));
        request.setTotalFee(order.getTotalFee());
        request.setSpbillCreateIp(StringUtils.trimToNull(order.getSpbillCreateIp()));
        request.setGoodsTag(StringUtils.trimToNull(order.getGoodsTag()));
        request.setProductId(StringUtils.trimToNull(order.getProductId()));
        request.setLimitPay(StringUtils.trimToNull(order.getLimitPay()));
        request.setOpenid(StringUtils.trimToNull(order.getOpenid()));
        return request;
    }

    /**
     * <pre>
     * 微信支付-申请退款
     * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
     * 接口链接：https://api.mch.weixin.qq.com/secapi/pay/refund
     * </pre>
     *
     * @return 退款操作结果
     */
    @Override
    public boolean refund(PayRefundDto refundDto) {
        if (null != payCallbackService) {
            PayOrderRefund refund = payCallbackService.refund(refundDto);
            if (null != refund) {
                WxPayRefundRequest request = new DtoConverter() {
                }.getEntity(refund, WxPayRefundRequest.class);
                log.debug("PayOrderRefund {}", refund);
                Long payId = refundDto.getPayId();
                SysPayProperty payProperties = getPayProperties(payId);
                WxPayService wxPayService = this.getWxService(payId);
                request.setNotifyUrl(payProperties.getRefundNotifyUrl());
                try {
                    WxPayRefundResult result = wxPayService.refund(request);
                    log.debug("退款操作，提交微信处理结果 {}", result);
                } catch (WxPayException e) {
                    e.printStackTrace();
                    throw new ValueException("服务配置错误");
                }
                return true;
            }
        }
        return false;
    }

    /**
     * <pre>
     * 微信支付-查询退款
     * 应用场景：
     *  提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，用零钱支付的退款20分钟内到账，
     *  银行卡支付的退款3个工作日后重新查询退款状态。
     * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
     * 接口链接：https://api.mch.weixin.qq.com/pay/refundquery
     * </pre>
     * 以下四个参数四选一,查询的优先级是： refundId > outRefundNo > transactionId > outTradeNo
     *
     * @param query 查询退款
     * @return 退款信息
     */
    @Override
    public WxPayRefundQueryResult refundQuery(PayOrderRefundQuery query)
            throws WxPayException {
        return this.getWxService(query.getPayId()).refundQuery(
                StringUtils.trimToNull(query.getTransactionId()),
                StringUtils.trimToNull(query.getOutTradeNo()),
                StringUtils.trimToNull(query.getOutRefundNo()),
                StringUtils.trimToNull(query.getRefundId()));
    }

    /**
     * <pre>支付回调通知处理</pre>
     */
    @Override
    public String parseOrderNotifyResult(String xmlData, Long pid) throws WxPayException {
        final WxPayOrderNotifyResult result = this.getWxService(pid).parseOrderNotifyResult(xmlData);
        log.debug("支付回调通知处理 {}", result);
        if (null != payCallbackService) {
            //TODO 这里可以优化为mq处理
            boolean notifyResult = payCallbackService.parseOrderNotifyResult(result);
            if (!notifyResult) {
                log.debug("支付回调通知处理失败");
                return WxPayNotifyResponse.fail("失败");
            }
        }
        log.debug("支付回调通知处理成功");
        return WxPayNotifyResponse.success("成功");
    }

    /**
     * <pre>退款回调通知处理</pre>
     */
    @Override
    public String parseRefundNotifyResult(String xmlData, Long pid) throws WxPayException {
        final WxPayRefundNotifyResult result = this.getWxService(pid).parseRefundNotifyResult(xmlData);
        log.debug("退款回调通知处理 {}", result);
        if (null != payCallbackService) {
            boolean notifyResult = payCallbackService.parseRefundNotifyResult(result);
            if (!notifyResult) {
                log.debug("退款回调通知处理失败");
                return WxPayNotifyResponse.fail("失败");
            }
        }
        log.debug("退款回调通知处理成功");
        return WxPayNotifyResponse.success("成功");
    }

    /**
     * <pre>扫码支付回调通知处理</pre>
     */
    @Override
    public String parseScanPayNotifyResult(String xmlData, Long pid) throws WxPayException {
        final WxScanPayNotifyResult result = this.getWxService(pid).parseScanPayNotifyResult(xmlData);
        log.debug("扫码支付回调通知处理 {}", result);
        if (null != payCallbackService) {
            boolean notifyResult = payCallbackService.parseScanPayNotifyResult(result);
            if (!notifyResult) {
                log.debug("扫码支付回调通知处理失败");
                return WxPayNotifyResponse.fail("失败");
            }
        }
        log.debug("扫码支付回调通知处理成功");
        return WxPayNotifyResponse.success("成功");
    }

    /**
     * <pre>
     * 扫码支付模式一生成二维码的方法
     * 二维码中的内容为链接，形式为：
     * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
     * 其中XXXXX为商户需要填写的内容，商户将该链接生成二维码，如需要打印发布二维码，需要采用此格式。商户可调用第三方库生成二维码图片。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
     * </pre>
     *
     * @param productId  产品Id
     * @param logoFile   商户logo图片的文件对象，可以为空
     * @param sideLength 要生成的二维码的边长，如果为空，则取默认值400
     * @return 生成的二维码的字节数组
     */
    @Override
    public byte[] createScanPayQrcodeMode1(String productId, File logoFile, Integer sideLength, Long pid) {
        return this.getWxService(pid).createScanPayQrcodeMode1(productId, logoFile, sideLength);
    }

    /**
     * <pre>
     * 扫码支付模式一生成二维码的方法
     * 二维码中的内容为链接，形式为：
     * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
     * 其中XXXXX为商户需要填写的内容，商户将该链接生成二维码，如需要打印发布二维码，需要采用此格式。商户可调用第三方库生成二维码图片。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
     * </pre>
     *
     * @param productId 产品Id
     * @return 生成的二维码URL连接
     */
    @Override
    public String createScanPayQrcodeMode1(String productId, Long pid) {
        return this.getWxService(pid).createScanPayQrcodeMode1(productId);
    }

    /**
     * <pre>
     * 扫码支付模式二生成二维码的方法
     * 对应链接格式：weixin：//wxpay/bizpayurl?sr=XXXXX。请商户调用第三方库将code_url生成二维码图片。
     * 该模式链接较短，生成的二维码打印到结账小票上的识别率较高。
     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
     * </pre>
     *
     * @param codeUrl    微信返回的交易会话的二维码链接
     * @param logoFile   商户logo图片的文件对象，可以为空
     * @param sideLength 要生成的二维码的边长，如果为空，则取默认值400
     * @return 生成的二维码的字节数组
     */
    @Override
    public byte[] createScanPayQrcodeMode2(String codeUrl, File logoFile, Integer sideLength, Long pid) {
        return this.getWxService(pid).createScanPayQrcodeMode2(codeUrl, logoFile, sideLength);
    }

    /**
     * 发送微信红包给个人用户
     * <pre>
     * 文档详见:
     * 发送普通红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
     *  接口地址：https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
     * 发送裂变红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4
     *  接口地址：https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack
     * </pre>
     *
     * @param redpack 请求对象
     */
    @Override
    public WxPaySendRedpackResult sendRedpack(PaySendRedpack redpack) throws WxPayException {
        WxPaySendRedpackRequest request = new WxPaySendRedpackRequest();
        request.setMchBillNo(redpack.getMchBillNo());
        request.setSendName(redpack.getSendName());
        request.setReOpenid(redpack.getReOpenid());
        request.setTotalAmount(redpack.getTotalAmount());
        request.setTotalNum(redpack.getTotalNum());
        request.setAmtType(redpack.getAmtType());
        request.setWishing(redpack.getWishing());
        request.setClientIp(redpack.getClientIp());
        request.setActName(redpack.getActName());
        request.setRemark(redpack.getRemark());
        request.setWxAppid(redpack.getWxAppid());
        request.setSceneId(redpack.getSceneId());
        request.setRiskInfo(redpack.getRiskInfo());
        request.setConsumeMchId(redpack.getConsumeMchId());
        return this.getWxService(redpack.getPayId()).sendRedpack(request);
    }

    /**
     * <pre>
     *   查询红包记录
     *   用于商户对已发放的红包进行查询红包的具体信息，可支持普通红包和裂变包。
     *   请求Url	https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo
     *   是否需要证书	是（证书及使用说明详见商户证书）
     *   请求方式	POST
     * </pre>
     *
     * @param mchBillNo 商户发放红包的商户订单号，比如10000098201411111234567890
     */
    @Override
    public WxPayRedpackQueryResult queryRedpack(String mchBillNo, Long pid) throws WxPayException {
        return this.getWxService(pid).queryRedpack(StringUtils.trimToNull(mchBillNo));
    }

    /**
     * <pre>
     * 交易保障
     * 应用场景：
     *  商户在调用微信支付提供的相关接口时，会得到微信支付返回的相关信息以及获得整个接口的响应时间。
     *  为提高整体的服务水平，协助商户一起提高服务质量，微信支付提供了相关接口调用耗时和返回信息的主动上报接口，
     *  微信支付可以根据商户侧上报的数据进一步优化网络部署，完善服务监控，和商户更好的协作为用户提供更好的业务体验。
     * 接口地址： https://api.mch.weixin.qq.com/payitil/report
     * 是否需要证书：不需要
     * </pre>
     */
    @Override
    public void report(PayReport report) throws WxPayException {
        WxPayReportRequest request = new WxPayReportRequest();
        request.setErrCode(StringUtils.trimToNull(report.getErrCode()));
        report.setDeviceInfo(StringUtils.trimToNull(report.getDeviceInfo()));
        report.setErrCodeDes(StringUtils.trimToNull(report.getErrCodeDes()));
        report.setExecuteTime(report.getExecuteTime());
        report.setInterfaceUrl(StringUtils.trimToNull(report.getInterfaceUrl()));
        report.setOutTradeNo(StringUtils.trimToNull(report.getOutTradeNo()));
        report.setResultCode(StringUtils.trimToNull(report.getResultCode()));
        report.setReturnCode(StringUtils.trimToNull(report.getReturnCode()));
        report.setReturnMsg(StringUtils.trimToNull(report.getReturnMsg()));
        report.setUserIp(StringUtils.trimToNull(report.getUserIp()));
        report.setTime(StringUtils.trimToNull(report.getTime()));
        this.getWxService(report.getPayId()).report(request);
    }

    /**
     * <pre>
     * 下载对账单
     * 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致，通过对账单核对后可校正支付状态。
     * 注意：
     * 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中，跟原支付单订单号一致，bill_type为REVOKED；
     * 2、微信在次日9点启动生成前一天的对账单，建议商户10点后再获取；
     * 3、对账单中涉及金额的字段单位为“元”。
     * 4、对账单接口只能下载三个月以内的账单。
     * 接口链接：https://api.mch.weixin.qq.com/pay/downloadbill
     * 详情请见: <a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_6">下载对账单</a>
     * </pre>
     *
     * @param bill 下载对账单
     * @return 保存到本地的临时文件
     */
    @Override
    public WxPayBillResult downloadBill(PayDownloadBill bill) throws WxPayException {
        WxPayDownloadBillRequest request = new WxPayDownloadBillRequest();
        request.setBillDate(StringUtils.trimToNull(bill.getBillDate()));
        request.setBillType(StringUtils.trimToNull(bill.getBillType()));
        request.setDeviceInfo(StringUtils.trimToNull(bill.getDeviceInfo()));
        request.setTarType(StringUtils.trimToNull(bill.getTarType()));
        return this.getWxService(bill.getPayId()).downloadBill(request);
    }

    /**
     * <pre>
     * 提交刷卡支付
     * 文档地址：https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
     * 应用场景：
     * 收银员使用扫码设备读取微信用户刷卡授权码以后，二维码或条码信息传送至商户收银台，由商户收银台或者商户后台调用该接口发起支付。
     * 提醒1：提交支付请求后微信会同步返回支付结果。当返回结果为“系统错误”时，商户系统等待5秒后调用【查询订单API】，查询支付实际交易结果；当返回结果为“USERPAYING”时，商户系统可设置间隔时间(建议10秒)重新查询支付结果，直到支付成功或超时(建议30秒)；
     * 提醒2：在调用查询接口返回后，如果交易状况不明晰，请调用【撤销订单API】，此时如果交易失败则关闭订单，该单不能再支付成功；如果交易成功，则将扣款退回到用户账户。当撤销无返回或错误时，请再次调用。注意：请勿扣款后立即调用【撤销订单API】,建议至少15秒后再调用。撤销订单API需要双向证书。
     * 接口地址：   https://api.mch.weixin.qq.com/pay/micropay
     * 是否需要证书：不需要。
     * </pre>
     */
    @Override
    public WxPayMicropayResult micropay(PayMicropay micropay) throws WxPayException {
        WxPayMicropayRequest request = new WxPayMicropayRequest();
        request.setBody(micropay.getBody());
        request.setDetail(micropay.getDetail());
        request.setAttach(micropay.getAttach());
        request.setOutTradeNo(micropay.getOutTradeNo());
        request.setTotalFee(micropay.getTotalFee());
        request.setFeeType(micropay.getFeeType());
        request.setSpbillCreateIp(micropay.getSpbillCreateIp());
        request.setGoodsTag(micropay.getGoodsTag());
        request.setLimitPay(micropay.getLimitPay());
        request.setTimeStart(micropay.getTimeStart());
        request.setTimeExpire(micropay.getTimeExpire());
        request.setAuthCode(micropay.getAuthCode());
        request.setSceneInfo(micropay.getSceneInfo());
        return this.getWxService(micropay.getPayId()).micropay(request);
    }

    /**
     * <pre>
     * 撤销订单API
     * 文档地址：https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3
     * 应用场景：
     *  支付交易返回失败或支付系统超时，调用该接口撤销交易。如果此订单用户支付失败，微信支付系统会将此订单关闭；如果用户支付成功，微信支付系统会将此订单资金退还给用户。
     *  注意：7天以内的交易单可调用撤销，其他正常支付的单如需实现相同功能请调用申请退款API。提交支付交易后调用【查询订单API】，没有明确的支付结果再调用【撤销订单API】。
     *  调用支付接口后请勿立即调用撤销订单API，建议支付后至少15s后再调用撤销订单接口。
     *  接口链接 ：https://api.mch.weixin.qq.com/secapi/pay/reverse
     *  是否需要证书：请求需要双向证书。
     * </pre>
     */
    @Override
    public WxPayOrderReverseResult reverseOrder(PayOrderReverse reverse) throws WxPayException {
        WxPayOrderReverseRequest request = new WxPayOrderReverseRequest();
        request.setOutTradeNo(StringUtils.trimToNull(reverse.getOutTradeNo()));
        request.setTransactionId(StringUtils.trimToNull(reverse.getTransactionId()));
        return this.getWxService(reverse.getPayId()).reverseOrder(request);
    }

    /**
     * <pre>获取沙箱环境签名key</pre>
     */
    @Override
    public String getSandboxSignKey(Long pid) throws WxPayException {
        return this.getWxService(pid).getSandboxSignKey();
    }

    /**
     * <pre>发放代金券</pre>
     */
    @Override
    public WxPayCouponSendResult sendCoupon(PayCouponSend couponSend)
            throws WxPayException {
        WxPayCouponSendRequest request = new WxPayCouponSendRequest();
        request.setType(StringUtils.trimToNull(couponSend.getType()));
        request.setVersion(StringUtils.trimToNull(couponSend.getVersion()));
        request.setCouponStockId(StringUtils.trimToNull(couponSend.getCouponStockId()));
        request.setDeviceInfo(StringUtils.trimToNull(couponSend.getDeviceInfo()));
        request.setOpenid(StringUtils.trimToNull(couponSend.getOpenid()));
        request.setOpenidCount(couponSend.getOpenidCount());
        request.setPartnerTradeNo(StringUtils.trimToNull(couponSend.getPartnerTradeNo()));
        request.setOpUserId(StringUtils.trimToNull(couponSend.getOpUserId()));
        return this.getWxService(couponSend.getPayId()).sendCoupon(request);
    }

    /**
     * <pre>查询代金券批次</pre>
     */
    @Override
    public WxPayCouponStockQueryResult queryCouponStock(PayCoupon coupon)
            throws WxPayException {
        WxPayCouponStockQueryRequest request = new WxPayCouponStockQueryRequest();
        request.setType(StringUtils.trimToNull(coupon.getType()));
        request.setVersion(StringUtils.trimToNull(coupon.getVersion()));
        request.setCouponStockId(StringUtils.trimToNull(coupon.getCouponStockId()));
        request.setDeviceInfo(StringUtils.trimToNull(coupon.getDeviceInfo()));
        request.setOpUserId(StringUtils.trimToNull(coupon.getOpUserId()));
        return this.getWxService(coupon.getPayId()).queryCouponStock(request);
    }

    /**
     * <pre>查询代金券信息</pre>
     */
    @Override
    public WxPayCouponInfoQueryResult queryCouponInfo(PayCouponInfoQuery query)
            throws WxPayException {
        WxPayCouponInfoQueryRequest request = new WxPayCouponInfoQueryRequest();
        request.setType(StringUtils.trimToNull(query.getType()));
        request.setVersion(StringUtils.trimToNull(query.getVersion()));
        request.setDeviceInfo(StringUtils.trimToNull(query.getDeviceInfo()));
        request.setOpenid(StringUtils.trimToNull(query.getOpenid()));
        request.setStockId(query.getStockId());
        request.setCouponId(StringUtils.trimToNull(query.getCouponId()));
        request.setOpUserId(StringUtils.trimToNull(query.getOpUserId()));
        return this.getWxService(query.getPayId()).queryCouponInfo(request);
    }

    /**
     * <pre>拉取订单评价数据</pre>
     */
    @Override
    public String queryComment(Date beginDate, Date endDate, Integer offset, Integer limit, Long pid) throws WxPayException {
        return this.getWxService(pid).queryComment(beginDate, endDate, offset, limit);
    }

}

